using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System.Collections.ObjectModel;
using System.Collections;

namespace Oops.Xna.Framework.Graphics
{
	/// <summary>
	/// Represents an animation sequence of a <see cref="SkinningData"/> instance.
	/// </summary>
	public class AnimationClip
	{
		#region KeyframeCollection Declaration

		#endregion

		#region AnimationEventCollection Declaration

		public class AnimationEventCollection : ICollection<AnimationEvent>, IEnumerable<AnimationEvent>, IEnumerable
		{
			private List<AnimationEvent> items = new List<AnimationEvent>();

			/// <summary>
			/// Initializes a new instance of the <see cref="AnimationEventCollection"/> class.
			/// </summary>
			internal AnimationEventCollection() { }

			#region ICollection<AnimationEvent> Members

			public int Count
			{
				get { return this.items.Count; }
			}

			public bool IsReadOnly
			{
				get { return false; }
			}

			public AnimationEvent this[int index]
			{
				get { return this.items[index]; }
				set
				{
					if (value == null)
					{
						throw new ArgumentNullException();
					}

					if (this.items[index].Time == value.Time)
					{
						this.items[index] = value;
					}
					else
					{
						this.items.RemoveAt(index);
						this.Add(value);
					}
				}
			}

			public void Clear()
			{
				this.items.Clear();
			}

			public void Add(AnimationEvent item)
			{
				if (item == null)
				{
					throw new ArgumentNullException();
				}

				int index = this.items.BinarySearch(item);
				if (index >= 0)
				{
					while (index < this.items.Count && item.Time == this.items[index].Time)
					{
						index++;
					}
				}
				else
				{
					index = ~index;
				}

				this.items.Insert(index, item);
			}

			public bool Remove(AnimationEvent item)
			{
				return this.items.Remove(item);
			}

			public void RemoveAt(int index)
			{
				this.items.RemoveAt(index);
			}

			public int IndexOf(AnimationEvent item)
			{
				return this.items.IndexOf(item);
			}

			public bool Contains(AnimationEvent item)
			{
				return this.items.Contains(item);
			}

			public void CopyTo(AnimationEvent[] array, int arrayIndex)
			{
				this.items.CopyTo(array, arrayIndex);
			}

			#endregion

			#region IEnumerable<AnimationEvent> Members

			public IEnumerator<AnimationEvent> GetEnumerator()
			{
				return this.items.GetEnumerator();
			}

			#endregion

			#region IEnumerable Members

			IEnumerator IEnumerable.GetEnumerator()
			{
				return this.items.GetEnumerator();
			}

			#endregion

			internal bool FindSegment(
				TimeSpan time0, 
				TimeSpan time1, 
				out int index, 
				out int count)
			{
				// Initializes the results.
				index = 0;
				count = 0;
					
				// Check for animation events. 
				if (this.items.Count == 0)
				{
					return false;
				}

				// Get the times in the right order.
				if (time0 > time1)
				{
					TimeSpan temp = time0;
					time0 = time1;
					time1 = temp;
				}

				int index0 = 0;
				while (index0 < this.items.Count && time0 > this.items[index0].Time)
				{
					index0++;
				}
				if (index0 >= this.items.Count)
				{
					return false;
				}

				int index1 = index0;
				while (index1 < this.items.Count && time1 > this.items[index1].Time)
				{
					count++;
					index1++;
				}

				if (count > 0)
				{
					index = index0;
					return true;
				}

				return false;
			}
		}

		#endregion

		private AnimationEventCollection events = new AnimationEventCollection();

		public AnimationClip(
			string name,
			TimeSpan duration, 
			int frameRate, 
			IList<Keyframe> keyframes, 
			int animationStartRow, 
			int animationEndRow)
		{
			this.Name = name;
			this.Duration = duration;
			this.FrameRate = frameRate;
			this.StartRow = animationStartRow;
			this.EndRow = animationEndRow;
			this.Keyframes = keyframes;
		}

		/// <summary>
		/// Gets the name of the <see cref="AnimationClip"/>.
		/// </summary>
		public string Name { get; private set; }
		
		/// <summary>
		/// Gets the duration of the <see cref="AnimationClip"/>.
		/// </summary>
		public TimeSpan Duration { get; private set; }
		
		/// <summary>
		/// Gets the frame rate of the <see cref="AnimationClip"/>.
		/// </summary>
		public int FrameRate { get; private set; }
		
		/// <summary>
		/// Gets the start texture index of the animation in the associated 
		/// <see cref="SkinningData.AnimationTexture"/>.
		/// </summary>
		public int StartRow { get; private set; }
		
		/// <summary>
		/// Gets the end texture index of the animation in the associated 
		/// <see cref="SkinningData.AnimationTexture"/>.
		/// </summary>
		public int EndRow { get; private set; }
		
		/// <summary>
		/// Gets the collection of <see cref="Keyframe"/> instances of the <see cref="AnimationClip"/>.
		/// </summary>
		public IList<Keyframe> Keyframes { get; private set; }
		
		/// <summary>
		/// Gets the collection of <see cref="AnimationEvent"/> instances for the <see cref="AnimationClip"/>.
		/// </summary>
		public AnimationEventCollection Events
		{
			get { return this.events; }
		}

		internal static AnimationClip Read(ContentReader input)
		{
			return new AnimationClip(
				input.ReadString(), 
				input.ReadObject<TimeSpan>(), 
				input.ReadObject<int>(), 
				input.ReadObject<IList<Keyframe>>(), 
				input.ReadObject<int>(), 
				input.ReadObject<int>());
		}
	}

	/// <summary>
	/// Worker for reading a <see cref="AnimationClip"/> instance from a binary format.
	/// </summary>
	public class AnimationClipReader : ContentTypeReader<AnimationClip>
	{
		#region ContentTypeReader<AnimationClip> Members

		protected override AnimationClip Read(ContentReader input, AnimationClip existingInstance)
		{
			return AnimationClip.Read(input);
		}

		#endregion
	}
}
